#!/usr/bin/env python

import os
import os.path
import ConfigParser
import sha
import base64
import random
import sets


ADMIN_GROUP='admins'
ADMIN_USERS= ['danderso']

TRAC_TEMPLATE_DIR = '/usr/local/share/trac/templates'


class ProjectError(Exception):
    """General error for this module"""


class SvnAuthzFile(object):
    def __init__(self, authz_file):
        self.authz_file = authz_file
        self.authz = ConfigParser.SafeConfigParser()
        if os.path.isfile(authz_file):
            self.authz.read(authz_file)
        else:
            # Default empty authz config.
            self.authz.add_section('groups')
            self._set_group(ADMIN_GROUP, ADMIN_USERS)
            self.authz.add_section('/')
            self.authz.set('/', '@' + ADMIN_GROUP, 'rw')
            self.authz.set('/', '*', '')
            self.authz.write(open(authz_file, 'w'))

    def _set_group(self, group_name, users):
        self.authz.remove_option('groups', group_name)
        self.authz.set('groups', group_name, ','.join(users))

    def _project_section(self, project_name):
        return '%s:/' % project_name

    def add_project(self, project_name, users):
        self._set_group(project_name, users)
        project_section = self._project_section(project_name)
        self.authz.remove_section(project_section)
        self.authz.add_section(project_section)
        self.authz.set(project_section, '@%s' % project_name, 'rw')

    def delete_project(self, project_name):
        self.authz.remove_option('groups', project_name)
        self.authz.remove_section(self._project_section(project_name))

    def refresh_admins(self):
        self._set_group(ADMIN_GROUP, ADMIN_USERS)

    def save(self):
        self.authz.write(open(self.authz_file, 'w'))

class AuthnFile(object):
    def __init__(self, authn_file):
        self.authn_file = authn_file
        self.authn = {}
        if os.path.isfile(authn_file):
            for line in open(authn_file):
                login, passwd = line.split(':', 1)
                self.authn[login] = passwd

    def _mkpasswd(self, length=8):
        allchars = '23456qwertasdfgzxcvbQWERTASDFGZXCVB789yuiophjknmYUIPHJKLNM'
        return ''.join([random.choice(allchars) for _ in xrange(length)])

    def add_user(self, user, password=None):
        if not password:
            password = self._mkpasswd()

        passhash = base64.encodestring(sha.new(password).digest())

        self.authn[user] = '{SHA}%s' % passhash

        return password

    def del_user(self, user):
        del self.authn[user]

    def create_missing(self, users):
        created = {}
        for user in users:
            if user not in self.authn:
                created[user] = self.add_user(user)

        return created

    def save(self):
        user_list = self.authn.keys()
        user_list.sort()
        authn_file = open(self.authn_file, 'w')
        for user in user_list:
            authn_file.write('%s:%s' % (user, self.authn[user]))
        authn_file.close()


def _run_trac(project_path, *args):
    commandline = 'trac-admin %s %s' % (project_path, ' '.join(args))
    retval = os.system(commandline)
    if not os.WIFEXITED(retval) or os.WEXITSTATUS(retval) != 0:
        raise ProjectError, 'Error running trac'


class TracAuthz(object):
    def __init__(self, trac_root):
        self.trac_root = trac_root

    def set_permissions(self, project_name, users):
        project_path = os.path.abspath(
            os.path.join(self.trac_root, project_name))
        if not os.path.isdir(project_path):
            raise ProjectError, 'Project does not exist'

        _run_trac(project_path, "permission remove '*' '*'")
        for user in sets.Set(users+ADMIN_USERS):
            _run_trac(project_path, 'permission add', user, 'TRAC_ADMIN')

class ProjetuManager(object):
    def __init__(self, project_root, svn_root, trac_root,
                 authn_file, authz_file):
        self.authn = AuthnFile(authn_file)
        self.svn_authz = SvnAuthzFile(authz_file)
        self.trac_authz = TracAuthz(trac_root)

        self.project_root = project_root
        self.svn_root = svn_root
        self.trac_root = trac_root

    def _project_name(self, term, course, founder):
        return '%s-%s-%s' % (term.upper(), course.upper(), founder.lower())

    def _svn_repo_path(self, project_name):
        return os.path.join(self.project_root, project_name, 'svn')

    def _trac_env_path(self, project_name):
        return os.path.join(self.project_root, project_name, 'trac')

    def _create_svn_repos(self, project_name):
        repo_path = self._svn_repo_path(project_name)
        retval = os.system('svnadmin create %s' % repo_path)
        if not os.WIFEXITED(retval) or os.WEXITSTATUS(retval) != 0:
            raise ProjectError, 'Error running svnadmin'
        os.symlink(repo_path, os.path.join(self.svn_root, project_name))
        commandline = ('svn mkdir file://%s/trunk file://%s/tags file://%s/branches '
                       '-m "Structure initiale" --username admin --non-interactive' %
                       (repo_path, repo_path, repo_path))
        retval = os.system(commandline)
        if not os.WIFEXITED(retval) or os.WEXITSTATUS(retval) != 0:
            raise ProjectError, 'Error running svnadmin'

    def _create_trac_project(self, project_name, project_print_name):
        project_path = self._trac_env_path(project_name)
        _run_trac(project_path, 'initenv', project_print_name,
                  'sqlite:db/trac.db svn', self._svn_repo_path(project_name),
                  TRAC_TEMPLATE_DIR)
        os.symlink(project_path, os.path.join(self.trac_root, project_name))

    def add_project(self, term, course, founder, users, save=True):
        project_name = self._project_name(term, course, founder)
        project_path = os.path.join(self.project_root, project_name)
        if (os.path.isdir(project_path)
            or os.path.isdir(os.path.join(self.svn_root, project_name))
            or os.path.isdir(os.path.join(self.trac_root, project_name))):
            raise ProjectError, "Project creation obstructed."

        os.mkdir(project_path)
        self._create_svn_repos(project_name)
        self._create_trac_project(project_name, '%s-%s' % (course, term))

        # Create any missing users
        if founder not in users:
            users.append(founder)
        new_users = self.authn.create_missing(users)

        # Set correct user permissions
        self.svn_authz.add_project(project_name, users)
        self.trac_authz.set_permissions(project_name, users)

        if save: # Jesus saves, passes to Moses, scoooore!
            self.authn.save()
            self.svn_authz.save()

        return new_users

PROJECT_ROOT = '/srv/projetu/projets'
SVN_ROOT = '/srv/projetu/svn'
TRAC_ROOT = '/srv/projetu/trac'
AUTHN_FILE = '/srv/projetu/authn.conf'
AUTHZ_FILE = '/srv/projetu/authz.conf'

# Change this every term.
TERM = 'P2007'


def mass_create_demo(proj):
    accounts = {}

    for i in xrange(1,31):
        accounts.update(proj.add_project(TERM, 'lolut', 'demo%d' % i, ['autre']))

    print "Demo accounts:"
    users = accounts.keys()
    users.sort()
    for user in users:
        print ' %s : %s' % (user, accounts[user])


if __name__ == '__main__':
    import sys
    if len(sys.argv) < 3:
        print 'Usage: %s <course> <founder> [<user> ...]' % sys.argv[0]
        sys.exit(1)

    course, founder = sys.argv[1:3]
    users = sys.argv[3:]

    proj = ProjetuManager(PROJECT_ROOT, SVN_ROOT, TRAC_ROOT, AUTHN_FILE, AUTHZ_FILE)

    #mass_create_demo(proj)

    new_users = proj.add_project(TERM, course, founder, users)

    print "New accounts:"
    for user,password in new_users.items():
        print " %s: %s" % (user, password)
